#include "shader.h"

#include <boost/shared_array.hpp>

#include "gl_util.h"

using namespace boost;
using namespace boost::numeric::ublas;

template<typename T, std::size_t N>
shared_array<T> buffer(const bounded_array<T, N> &barray) {
	shared_array<T> buffer(new T[N]);

	for(typename bounded_array<T, N>::size_type i=0; i<N; i++)
		buffer[i]=barray[i];

	return buffer;
}

bool compilationSucceeded(GLhandleARB object) {
	GLint rval;
	glGetObjectParameterivARB(object, GL_OBJECT_COMPILE_STATUS_ARB, &rval); 
	return rval == 1;
}

bool linkingSucceeded(GLhandleARB object) {
	GLint rval;
	glGetObjectParameterivARB(object, GL_OBJECT_LINK_STATUS_ARB, &rval); 
	return rval == 1;
}

string getInfoLog(GLhandleARB handle, GLenum type) {
	string log;

	GLsizei size;

	glGetObjectParameterivARB(handle, GL_OBJECT_INFO_LOG_LENGTH_ARB, &size);
	if(size > 0) {
		shared_array<char> buffer(new char[size]);

		GLint used;
		glGetInfoLogARB(handle, size, &used, buffer.get());

		log.append(buffer.get(), used);
	}

	return log;
}

Shader::Shader() 
: linked(false), program(glCreateProgramObjectARB()) 
{}

string Shader::link() {
	glLinkProgramARB(program);

	linked=linkingSucceeded(program);
	if(linked)
		return string();
	else
		return getInfoLog(program, GL_OBJECT_LINK_STATUS_ARB);
}

void Shader::start() {
	if(linked) {
		glUseProgramObjectARB(program);
		check();
	}
}

void Shader::stop() {
	if(linked) {
		glUseProgramObjectARB(0);
		check();
	}
}

string Shader::attachSource(const char *src, GLenum type) {
	GLhandleARB obj=glCreateShaderObjectARB(type);
	glShaderSourceARB(obj, 1, &src, NULL);
	glCompileShaderARB(obj);
	glAttachObjectARB(program, obj);

	string log=getInfoLog(obj, GL_OBJECT_COMPILE_STATUS_ARB);
	cout << log << endl;

	if(compilationSucceeded(obj))
		return string();
	else
		return log;
}

void Shader::setUniform(const char *name, float f) {
	if(linked)
	{
		GLhandleARB handle=glGetUniformLocationARB(program, name); check();
		glUniform1f(handle, f); check();
	}
}

void Shader::setUniform(const char *name, vec2 &v) {
	if(linked)
	{
		GLhandleARB handle=glGetUniformLocationARB(program, name); check();
		glUniform2f(handle, v[0], v[1]); check();
	}
}

void Shader::setUniform(const char *name, vec3 &v) {
	if(linked)
	{
		GLhandleARB handle=glGetUniformLocationARB(program, name); check();
		glUniform3f(handle, v[0], v[1], v[2]); check();
	}
}

void Shader::setUniform(const char *name, vec4 &v) {
	if(linked)
	{
		GLhandleARB handle=glGetUniformLocationARB(program, name); check();
		glUniform4f(handle, v[0], v[1], v[2], v[3]); check();
	}
}

void Shader::setUniform(const char *name, mat4 &m) {
	if(linked)
	{
		GLhandleARB handle=glGetUniformLocationARB(program, name); check();
		glUniformMatrix4fvARB(handle, 1, false, buffer(m.data()).get()); 
		check();
	}
}